use crate::cg::prelude::*;
use skia_safe;

pub fn textstyle(
    style: &TextStyleRec,
    _ctx: &Option<TextStyleRecBuildContext>,
) -> skia_safe::textlayout::TextStyle {
    let mut ts = skia_safe::textlayout::TextStyle::new();
    let default_ctx = TextStyleRecBuildContext::default();
    let ctx = _ctx.as_ref().unwrap_or(&default_ctx);

    // [decoration]
    let decoration =
        TextDecoration::from_with_context(style.text_decoration.unwrap_or_default(), &ctx.into())
            .into();

    // [font_style]
    let font_style = skia_safe::FontStyle::new(
        skia_safe::font_style::Weight::from(style.font_weight.value() as i32),
        skia_safe::font_style::Width::NORMAL,
        if style.font_style_italic {
            skia_safe::font_style::Slant::Italic
        } else {
            skia_safe::font_style::Slant::Upright
        },
    );

    // [variables]
    let mut coords = vec![];

    // variables::[1]. apply font variations
    if let Some(vars) = &style.font_variations {
        for v in vars {
            let tag = tag_from_str(&v.axis);
            coords.push(skia_safe::font_arguments::variation_position::Coordinate {
                axis: tag,
                value: v.value,
            });
        }
    }

    // higher-level attribute overrides
    // variables::[2.1]. apply font weight
    coords.push(var_wght(style.font_weight.value() as f32));

    // variables::[2.2]. apply font width
    if let Some(wdth) = style.font_width {
        coords.push(var_wdth(wdth));
    }

    // variables::[2.3]. apply font optical sizing
    match style.font_optical_sizing {
        FontOpticalSizing::Auto => coords.push(var_opsz(style.font_size)),
        FontOpticalSizing::Fixed(v) => coords.push(var_opsz(v)),
        FontOpticalSizing::None => {}
    }

    let variation_position = skia_safe::font_arguments::VariationPosition {
        coordinates: coords.as_slice(),
    };
    let font_args =
        skia_safe::FontArguments::new().set_variation_design_position(variation_position);

    //
    ts.set_font_size(style.font_size);

    // letter_spacing
    // map
    let letter_spacing = match style.letter_spacing {
        TextLetterSpacing::Fixed(px) => px,
        TextLetterSpacing::Factor(factor) => factor * style.font_size,
    };

    if letter_spacing != 0.0 {
        ts.set_letter_spacing(letter_spacing);
    }

    // word_spacing
    // map
    let word_spacing = match style.word_spacing {
        TextWordSpacing::Fixed(px) => px,
        TextWordSpacing::Factor(factor) => factor * style.font_size,
    };

    if word_spacing != 0.0 {
        ts.set_word_spacing(word_spacing);
    }

    /// skia will not support 0.0 or below 0.001
    /// https://github.com/rust-skia/rust-skia/issues/1203
    const MIN_LINE_HEIGHT_FACTOR: f32 = 0.01;
    match style.line_height {
        TextLineHeight::Normal => {
            ts.set_height_override(false);
            // CSS `line-height: normal`
            // → use font metrics; don't set height; don't override.
            // convention: -1 as "unset" (not required)
            ts.set_height(-1.0);
        }
        TextLineHeight::Fixed(px) => {
            ts.set_height_override(true); // force exact spacing (even < metrics)
                                          // CSS `line-height: 12px` (absolute)
                                          // Skia expects a multiplier, so convert: factor = px / fontSize

            let fs = style.font_size.max(f32::EPSILON); // avoid div-by-zero
            let factor = (px / fs).max(MIN_LINE_HEIGHT_FACTOR);
            ts.set_height(factor);
        }
        TextLineHeight::Factor(mult) => {
            ts.set_height_override(true); // force exact spacing
                                          // CSS `line-height: 120%`
                                          // Percent is already a factor of font size.
            let factor = (mult).max(MIN_LINE_HEIGHT_FACTOR);
            ts.set_height(factor);
        }
    }
    ts.set_decoration(&decoration);
    let mut families: Vec<&str> = Vec::with_capacity(1 + ctx.user_fallback_fonts.len());
    families.push(&style.font_family);
    for f in &ctx.user_fallback_fonts {
        families.push(f.as_str());
    }
    ts.set_font_families(&families);
    ts.set_font_arguments(&font_args);
    ts.set_font_style(font_style);
    if let Some(features) = &style.font_features {
        for feature in features {
            ts.add_font_feature(feature.tag.clone(), if feature.value { 1 } else { 0 });
        }
    }

    // super override kerning for [font_kerning]
    ts.add_font_feature("kern", if style.font_kerning { 1 } else { 0 });

    ts
}

fn var_wght(wght: f32) -> skia_safe::font_arguments::variation_position::Coordinate {
    skia_safe::font_arguments::variation_position::Coordinate {
        axis: skia_safe::FourByteTag::from(('w', 'g', 'h', 't')),
        value: wght,
    }
}

fn var_wdth(wdth: f32) -> skia_safe::font_arguments::variation_position::Coordinate {
    skia_safe::font_arguments::variation_position::Coordinate {
        axis: skia_safe::FourByteTag::from(('w', 'd', 't', 'h')),
        value: wdth,
    }
}

fn var_opsz(opsz: f32) -> skia_safe::font_arguments::variation_position::Coordinate {
    skia_safe::font_arguments::variation_position::Coordinate {
        axis: skia_safe::FourByteTag::from(('o', 'p', 's', 'z')),
        value: opsz,
    }
}

fn tag_from_str(tag: &str) -> skia_safe::FourByteTag {
    let bytes = tag.as_bytes();
    let b0 = *bytes.get(0).unwrap_or(&b' ');
    let b1 = *bytes.get(1).unwrap_or(&b' ');
    let b2 = *bytes.get(2).unwrap_or(&b' ');
    let b3 = *bytes.get(3).unwrap_or(&b' ');
    skia_safe::FourByteTag::from((b0 as char, b1 as char, b2 as char, b3 as char))
}
